/*
 * Transmissão de Dados - Trabalho 3
 *
 * Alex Rodrigo de Oliveira      - 06/30403
 * Danilo Gaby Andersen Trindade - 06/82039
 */

/* Bibliotecas incluídas */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <time.h>
#include "checksum.h"
#include "segments.h"

#define DESV_RTT 0.1

int main() {
	int sock, yes = 1, aux;
	struct sockaddr_in addr;
	unsigned int size = sizeof addr;
	unsigned int randResult = 0;
	Ack *ultimoAck;
	Segment *segmento;
	Handshake *handshake;
	uint16_t rtt;
	int desv = 0;


	/* Muda o seed do random. */
	srand(time(NULL));


	/* Cria o socket. */
	sock = socket(PF_INET, SOCK_DGRAM, 0);
	if (sock == -1) {
		perror("Não conseguiu criar o socket do servidor.");
		return EXIT_FAILURE;
	}

	/* Permite que o socket escute na porta, a não ser que já tenha alguém escutando nela. */
	aux = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes);
	if (aux == -1) {
		perror("Não conseguiu reutilizar a porta de conexão.");
		return EXIT_FAILURE;
	}

	/* Cria o endereço do socket. */
	addr.sin_family = AF_INET;
	addr.sin_port = htons(SERVER_PORT);
	addr.sin_addr.s_addr = INADDR_ANY;
	memset(&addr.sin_zero, 0, 8);


	/* Associa o socket ao seu endereço. */
	if (bind(sock, (struct sockaddr *) &addr, sizeof addr) == -1) {
		perror("Não conseguiu fazer o bind do socket do servidor com a porta");
		return EXIT_FAILURE;
	}

	/* Aloca espaco em memoria. */
	ultimoAck = malloc(sizeof(Ack));
	handshake = malloc(sizeof(Handshake));
	segmento = malloc(sizeof(Segment));

	start:

	/* Reseta o ultimo ack para 0 */
	ultimoAck->ack = 0;
	ultimoAck->checksum = 0;


	/* Aguarda a conexao ser estabelecida e realiza o handshake. */
	aux = recvfrom(sock, handshake, sizeof(Handshake), 0,
			(struct sockaddr *) &addr, &size);
	if (aux <= 0) {
		perror("Erro ao receber mensagem de handshake");
		return EXIT_FAILURE;
	}
	if (handshake->erro > 100) goto start;
	printf("\nHandshake recebido. RTT: %dms, taxa de erro: %d%%\n",
				handshake->rtt, handshake->erro);

	/* Envia Ack 0 para confirmar o handshake. */
	aux = sendto(sock, ultimoAck, sizeof(Ack), 0, (struct sockaddr *) &addr,
			size);
	if (aux <= 0) {
		perror("Erro ao enviar mensagem resposta de handshake");
		return EXIT_FAILURE;
	}

	/* Prepara para a simulação do RTT */
	if (handshake->rtt >= 10){
		desv =  (int) (DESV_RTT * (float)handshake->rtt);
	} else {
		desv = 1;
	}

		/* Fica em loop infinito recebendo e enviando de volta segmentos ao cliente */
		for (;;) {
			/* Espera receber algum segmento. */
			aux = recvfrom(sock, segmento, sizeof(Segment), 0,
					(struct sockaddr *) &addr, &size);
			if (aux <= 0) {
				perror("Erro ao receber mensagem");
				return EXIT_FAILURE;
			}
			/* Gera um numero aleatorio para simular um erro. */
			randResult = (rand() % 100);
			if (randResult >= handshake->erro) {
				/* Se passar a simulacao do erro verifica o checksum do segmento. */
				if (verify_checksum(segmento, sizeof(Segment))) {
					/* Passando checksum, verifica se esta na sequencia correta. */
					if (segmento->seqnum == ((ultimoAck->ack) + 1)) {
						/* Se estiver na sequencia, prepara ack para enviar de volta. */
						ultimoAck->ack++;
						ultimoAck->checksum = calc_checksum(ultimoAck,sizeof(Ack));
				} else {
					/* Se o segmento for de um ack menor, entao reseta ack para o menor
					 * pois provavelmente foi timeout. */
					if (segmento->seqnum <= ultimoAck->ack){
						ultimoAck->ack = segmento->seqnum;
						ultimoAck->checksum = calc_checksum(ultimoAck, sizeof(Ack));
					} else {
						/* Se nao estiver na sequencia, entao perdeu segmento e nao atualiza ack. */
						puts("Pacote perdido!");
					}
				}
			} else {
				/* Se nao passou o checksum, segmento foi corrompido. */
				puts("Pacote recebido corrompido!");
			}

			/* Simula o RTT de acordo com informacao recebida pelo handshake. */
			rtt = handshake->rtt + (rand() % desv) - (rand() % desv);
			struct timeval time2;
			gettimeofday(&time2, NULL);
			uint32_t time1 = to_ms(time2);
			do {
				gettimeofday(&time2, NULL);
			} while (to_ms(time2) - time1 < rtt);


			/* Envia o Ack ao cliente. */
			aux = sendto(sock, ultimoAck, sizeof(Ack), 0,
					(struct sockaddr *) &addr, size);
			if (aux <= 0) {
				perror("Erro ao enviar mensagem");
				return EXIT_FAILURE;
			}

			/* Imprime a mensagem recebida no pacote. */
			printf("pacote %d\n", segmento->seqnum);

			if (ultimoAck->ack == NUMPKTS)
				goto start;
		}
	}

	/* Libera memoria alocada. */
	close(sock);
	free(ultimoAck);
	free(handshake);
	free(segmento);
	return 0;
}
